﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Duellum.Core;
using Duellum.Map;
using JpLabs.Geometry;
using JpLabs.Extensions;
using Duellum.Game.Commands;

namespace Duellum.Game
{
	public enum DuelGameState
	{
		Created,
		Initialized,
		WaitingForPlayer,
		Playing,
		GameOver
	}

	public class DuelGame
	{
		public DuelGameState GameState { get; private set; }
		
		private LinkedList<DuelPlayer> playerCycle;
		//private LinkedListNode<DuelPlayer> currentPlayerLinkedNode;

		public ICollection<DuelPlayer> Players { get; private set; }
		public DuelMap Map { get; private set; }

		public DuelGame(DuelMap map, IEnumerable<DuelPlayer> players)
		{
			if (map == null) throw new ArgumentNullException("map");
			if (players == null) throw new ArgumentNullException("players");
		
			this.GameState = DuelGameState.Created;
			this.Map = map;
			//this.Players = players.Select(pp => pp.CreateObj() as DuelPlayer).ToReadOnlyColl();
			this.Players = players.ToReadOnlyColl();

			if (this.Players.Count < 2) throw new ArgumentException("At least two players are required", "players");
		}		

		public DuelPlayer CurrentPlayer
		{
			get {
				//return (currentPlayerLinkedNode == null) ? null : currentPlayerLinkedNode.Value;
				var firstNode = playerCycle.First;
				return (firstNode == null) ? null : firstNode.Value;
			}
		}
		
		public IEnumerable<DuelPlayer> GetPlayerQueue()
		{
			return playerCycle.ToArray();
		}
		
		private void AssertGameState(DuelGameState expectedState)
		{
			if (this.GameState != expectedState) throw new InvalidOperationException("Operation not allowed in current state");
		}

		public void Initialize(bool shufflePlayers)
		{
			AssertGameState(DuelGameState.Created);
			GameState = DuelGameState.Initialized;
			
			// From Duelo VC 3D
			//1º) Sortear Ordem;
			//2º) Embaralhar Poderes;
			//3º) Acionar/Sorteiar Poderes [obsoleto?];
			//4º) Posicionar;
			//5º) Acionar Monstro [obsoleto];
			//6º) Esconder conteúdo que só aparece no editor [obsoleto];
			//7º) Iniciar vez do primeiro (zera replay, zera 'tempo');
			//8º) Chamar o primeiro.

			/// - Initialize players (powers, colors, etc.)
			/// - Shuffle player order
			/// - Place players
			
			//1. Shuffle player order
			if (shufflePlayers) this.Players = this.Players.Shuffle().ToReadOnlyColl();
			
			//2. TODO: Convert Map to play-mode (basically, generate random objects)
			
			//3. Initialize randomly initialized properties //TODO: how to apply this automaticaly?
			//{
			//    var rnd = new Random();
			//    foreach (var player in Players) player.FaceAngleInt = rnd.Next(0, 11);
			//}
			
			//4. Generate players positions	//TODO: might depend on players' sizes, not only on their count
			var positions = Map.GenerateRandomStartPositons(Players.Count);
			
			//5. Apply positions and insert players into the map
			foreach (var player in Players) player.Map = this.Map;
			Players.Zip(positions, (player, pos) => player.Position = pos).Last();
			//foreach (var player in Players) Map.AddEntry(player, player.Position.Value);
			
			//6. Create player cycled linked list
			playerCycle = new LinkedList<DuelPlayer>(this.Players);
		}
		
		public void StartFirstTurn()
		{
			AssertGameState(DuelGameState.Initialized);
			GameState = DuelGameState.WaitingForPlayer;
			
			//TODO: Update game status
			//TODO: Start replay recording
		}
		
		public void StartTurn()
		{
			AssertGameState(DuelGameState.WaitingForPlayer);
			GameState = DuelGameState.Playing;

			var player = this.CurrentPlayer;
			
			///TODO: 
			///	- is Player alive?
			///		- check Alien infection, and apply shape shift
			///		- apply regeneration (or degeneration)
			///		- bleed
			///	- is Player recently deceased?
			///		- show last replay and remove player from activePlayerList
			///	- is Player still alive?
			///		- recharge stuff that is automatically recharges (e.g.: mana)
			///		- discharge stuff that is discharging (e.g.: invisibility helmet)
			///		- reset player's TimeUnits
			///		- start to record a new turn for replay purposes
			///		- reset CurrentTurnFog
			///		- perceive map
			
			player.PerceiveMap(this.Map);
			
		}
		
		public void EndTurn()
		{
			AssertGameState(DuelGameState.Playing);
			GameState = DuelGameState.WaitingForPlayer;
			
			CycleToNextPlayer();
			
			//TODO: run Map RunningEffects (e.g.: disperse smoke, burn fire, close doors, burn acid, bomb countdowns, etc.)
			//REMARK: dead players should still be considered when running map effects
			
						
		}
		
		private void CycleToNextPlayer()
		{
			var firstPlayerNode = playerCycle.First;
			
			playerCycle.Remove(firstPlayerNode);
			
			playerCycle.AddLast(firstPlayerNode);
		}
		
		public IEnumerable<IDuelCommand> GetAvailableCommands()
		{
			//TODO: has to reuse command instances to be able to provide CanExecuteChanged event
						
			return new IDuelCommand[] {
				new TurnCommand(this),
				new MoveCommand(this)
			};
		}

		public IDuelCommand GetCommand(DuelTypeId cmdId)
		{
			return GetAvailableCommands().Where(cmd => cmd.Id == cmdId).FirstOrDefault();
		}

		//public void DoAction(IDuelCommand command, object cmdParams)
		//{
		//    //TODO: code this properly, and in the appropriate place
		//}
	}
}
